using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using Wolfpack.Contrib.BuildAnalytics.Interfaces.Entities;
using Wolfpack.Core;
using Wolfpack.Core.Interfaces.Entities;
using Wolfpack.Core.Notification;
using Wolfpack.Core.Publishers;

namespace Wolfpack.Contrib.BuildAnalytics.Publishers
{
    public abstract class BuildNotificationPublisherBase<T> : FilteredResultPublisherBase<T>
        where T : BuildParserConfigBase
    {
        protected BuildNotificationPublisherBase(T config, string friendlyName)
            : base(config, friendlyName)
        {
        }

        protected BuildNotificationPublisherBase(T config, Func<NotificationEvent, bool> filter) : base(config, filter)
        {
        }

        /// <summary>
        /// This ensures we only invoke the parser if the build is successful AND matches the target name
        /// </summary>
        /// <param name="notification"></param>
        /// <returns></returns>
        protected override bool MatchOnFriendlyName(NotificationEvent notification)
        {
            if (Config.MatchBuildResult.HasValue)
            {
                if (Config.MatchBuildResult.Value != notification.Result)
                    return false;
            }

            return base.MatchOnFriendlyName(notification);
        }

        /// <summary>
        /// This is responsible for building the fully qualified filename to a build artifact
        /// </summary>
        /// <param name="buildNotification"></param>
        /// <param name="template"></param>
        /// <returns></returns>
        protected virtual string BuildArtifactFilename(NotificationEvent buildNotification, string template)
        {
            if (buildNotification.Tags == null || !buildNotification.Tags.Any())
                return template;

            var filename = template;
            var rx = new Regex("({buildid})", RegexOptions.IgnoreCase);

            var buildid = buildNotification.Properties[NotificationProperties.BuildId];
            return rx.Replace(filename, buildid);
        }

        /// <summary>
        /// Gets the content of the report file or if stored in a zip then extracts it from there
        /// </summary>
        /// <param name="buildNotification"></param>
        /// <param name="defaultFilename"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        protected virtual bool GetContent(NotificationEvent buildNotification, string defaultFilename, out string content)
        {
            content = null;

            if (!string.IsNullOrWhiteSpace(Config.ZipFileTemplate))
            {
                var zipFilename = BuildArtifactFilename(buildNotification, Config.ZipFileTemplate);
                if (!File.Exists(zipFilename))
                    return false;

                ZipFile zf = null;

                try
                {
                    var relativeFilename = string.IsNullOrWhiteSpace(Config.ReportFileTemplate) 
                        ? defaultFilename
                        : BuildArtifactFilename(buildNotification, Config.ReportFileTemplate);
                    // zip folders are / delimited
                    relativeFilename = relativeFilename.Replace(@"\", "/");
                    
                    using (var fs = File.OpenRead(zipFilename))
                    {
                        zf = new ZipFile(fs);

                        foreach (ZipEntry zipEntry in zf)
                        {
                            if (!zipEntry.IsFile)
                                continue; // Ignore directories
                            if (!string.Equals(zipEntry.Name, relativeFilename, StringComparison.OrdinalIgnoreCase))
                                continue;

                            var buffer = new byte[4096]; // 4K is optimum
                            var zipStream = zf.GetInputStream(zipEntry);
                            
                            using (var ms = new MemoryStream())
                            {
                                StreamUtils.Copy(zipStream, ms, buffer);
                                ms.Seek(0, SeekOrigin.Begin);

                                using (var msr = new StreamReader(ms))
                                    content = msr.ReadToEnd();
                            }
                            break;
                        }
                    }
                }
                finally
                {
                    if (zf != null)
                    {
                        zf.IsStreamOwner = true; // Makes close also shut the underlying stream
                        zf.Close(); // Ensure we release resources
                    }
                }
            }
            else
            {
                var reportFilename = BuildArtifactFilename(buildNotification, Config.ReportFileTemplate);
                if (!File.Exists(reportFilename))
                    return false;

                using (var sr = new StreamReader(reportFilename))
                {
                    content = sr.ReadToEnd();
                }
            }

            return !string.IsNullOrWhiteSpace(content);
        }

        protected double? ExtractStat(string reportText)
        {
            var rx = new Regex(@"[-+]?\d*\.?\d+");
            var statText = rx.Match(reportText).Value;

            double stat;
            if (double.TryParse(statText, out stat))
                return stat;
            return null;
        }

        protected void PublishStat(NotificationEvent buildNotification, string category, double? stat, string name)
        {
            var checkId = string.Format("{0}-{1}", buildNotification.CheckId, category);

            var statData = HealthCheckData.For(new PluginDescriptor
                                                   {
                                                       Name = checkId
                                                   },
                                               "{0} statistic: {1}", category, name)
                .ResultCountIs(stat);

            var request = NotificationRequestBuilder.AlwaysPublish(statData).Build();
            var notification = NotificationHub.ConvertRequestToEvent(request);
            Messenger.Publish(notification);
        }
    }
}